<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>大文件分片上传</title>
</head>
<body>
  <input type="file">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/spark-md5/3.0.2/spark-md5.js"></script>
  <script>
    const inp = document.querySelector('input')
    /**
     * 大文件上传
     * 第一个核心技术点：
     * 文件分片：
     * file.slice  这和 array.slice 类似，这里是截取从第 start 个字节到 end-1 个字节， 返回的是一个 Blob 类型的数据, 
     * 而 Blob 类型是可以直接通过 ajax 发送给后台的，
     * ps: File、Blob 对象，只是保存了文件的基本信息，如: size , type, name, position, 只有通过 FileReader 才能读取到它的数据
     * 第二点：文件秒传
     * 场景：当遇到断网，页面刷新，导致要重新上传文件，该如何做
     * 难点：
     * 1. 如何知道上传的是同一个文件，（有可能文件名称/路径变了，）
     * 2. 已上传的进度
     * 解决：
     * hash算法: 能通过 hash 代表某一个内容，如果内容发生了变化， hash 值也会有所不同。 这里使用 md5
     *          不推荐使用 hash 整个文件，因为文件过大的话，如 100G 的数据读到内存中，再去算 hash，内存会吃不消
     *          这里推荐对分块进行 hash, 这种方法叫做增量算法，每次对一小部分进行计算
     * 一般来说，文件太大的话，不会放到主线程上去运行的，会放到 web worker 中去运算, 一些大站，如 b站，会将大文件分为几大块，
     * 一大块再分几小块去计算 hash,然后再上传，这一大块上传完毕后，在重复刚才的操作。 最后有空闲时间在合算 hash
     * 
     * 出自：抖音@渡一前端教科书 23/2/25
     * */ 
    inp.onchange = async function(e) {
      const file = inp.files[0];
      if (!file) return;
      // console.log(file)
      // const piece = file.slice(0, 100);  //截取 0-99 个字节，
      let chunks = createChunks(file, 10 * 1024 * 1024)
      // console.log(chunks)
      const result = await hash(chunks)
      console.log(result)
    }

    function createChunks(file, chunkSize) {
      const result = [];
      for(let i = 0; i < file.size; i+=chunkSize) {
        result.push(file.slice(i, i + chunkSize))
      }
      return result
    }

    function hash(chunks) {
      // 因为 reader.onload 和 hash 计算需要时间，是异步的，所以放到 promise 中去
      return new Promise(resolve => {
        const spark = new SparkMD5();
        function _read(i) {
          if (i >= chunks.length) {
            resolve(spark.end());  // 全部读取完成后，可以通过spark.end 输出整个的hash 计算
            return; // 读取完成
          }
          const blob = chunks[i];
          const reader = new FileReader();  
          reader.onload = e => {
            const bytes =  e.target.result;  // 读取到的字节数组
            spark.append(bytes)
            _read(i + 1)
          }
          reader.readAsArrayBuffer(blob)
        }
        _read(0)
      })
      
    }

    // 伪代码
    function upload(chunk) {
      const formdata = new FormData();
      formdata.append('0', chunk)
      formdata.append('filename', file,filename);
      
      const xhr = new XMLHttpRequest();
      xhr.addEventListener('load', function() {})
      xhr.open('post', '');
      xhr.send(formdata);
      xhr.addEventListener('progress', updateProgress)
      xhr.upload.addEventListener('progress', updateProgress)
      
      function updateProgress(event) {
        if (event.lengthComputable) {
          // 进度条
        }
      }
    }
  </script>
</body>
</html>